﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using log4net;
using BidFunLib.Services;
using BidFunLib.Entity;
using BidFunLib.EntityHelper;


namespace BidFunLib.BidWeb.Sale
{

    public delegate void OnLoginEventHandler();
    public delegate void OnBidListUpdateEventHandler();
    public delegate void OnLoggerUpdateEventHandler(BidLogger log);
    public delegate void OnBidEndedHandler(BidWebSaleEngine sale);
    public delegate void OnBidErrorHandler(BidWebSaleEngine sale);
    public delegate void OnStatsHander(StatsBidSale stats);
    public delegate void OnBrowserChange();


    /// <summary>
    /// Log all the data for a selected bid
    /// </summary>
    public abstract class BidWebSaleEngine
    {
        #region Static
        public static readonly ILog Log = LogManager.GetLogger(typeof(BidWebSaleEngine));

        public static DateTime TIME_NULL;
        
        public static String BidEndedString = "Terminé!";
        public static String BidVerifString = "Vérification...";

        public static Int32 DefaultSwitchInterval = 1000 * 60 * 7;
        public static Int32 normalTickTime = 400; // ms
        public static Int32 quickTickTime = 250; // ms
        public static Int32 MaxVerifTime = 20; // sec
        public static Int32 MaxVerifCpt = (Int32)(20 * 1000 / quickTickTime);

        #endregion

        public abstract Boolean checkForNewBid();

        public abstract Boolean postInitElement();

       
        #region Event

        public event OnBidEndedHandler bidEndedEvent;

        public event OnLoggerUpdateEventHandler OnLoggerUpdate;

        public event OnBidErrorHandler BidError;

        public event OnStatsHander StatsHander;

        public event OnBrowserChange OnBrowserChange;

        #endregion

        #region Members
        
        private Int32 switchBrowserInterval;

        public Int32 SwitchBrowserInterval
        {
          get 
          { 
              return switchBrowserInterval; 
          }
          set 
          { 
              switchBrowserInterval = value;
              if (SwitchBrowserTimer != null)
              {
                  SwitchBrowserTimer.Interval = switchBrowserInterval;
              }
          }
        }
        /// <summary>
        /// Key : Pseudo  , Value : BidUserWeb
        /// </summary>
        public Dictionary<String, BidUserWeb> MapBidUserWeb { get; set; }

        public Timer MyTimer { get; set; }

        public DateTime FirstVerification { get; set; }

        public DateTime BidEndedTime { get; set; }

        public HtmlDocument Document { get; set; }

        public BidSale Sale { get; set; }

        public Boolean Running { get; set; }

        public String LastBidAmount { get; set; }

        public Boolean IsEnded { get; set; }

        public String Url { get; set; }

        public Int32 NbBids { get; set; }

        public String LastUserName { get; set; }

        public String Span_time { get; set; }

        public String NBUserConnected { get; set; }

        private Boolean ERROR = false;


        #endregion

        #region HtmlMember

        //  String bid_item;
        public static String bid_span_time = "bid_span_time_";
        public static String bid_span_user = "bid_span_user_";
        public static String bid_span_amount = "bid_span_amount_";

        public HtmlElement HUser { get; set; }
        public HtmlElement HAmount { get; set; }

        public HtmlElement HConnected { get; set; }
        public HtmlElement HTimeLeft { get; set; }

        private HtmlElement[] hlast_bids_username = new HtmlElement[10];
        private HtmlElement[] hlast_bids_amount = new HtmlElement[10];
        private HtmlElement[] hlast_bids_type = new HtmlElement[10];
        private HtmlElement[] hlast_bids_datetime = new HtmlElement[10];

        private Boolean NewBid;

        #endregion

        #region WebBrowser

        public WebBrowser Browser
        {
            get
            {
                if (browser_1 == null && browser_2 == null)
                {
                    return null;
                }
                if (browser_1 == null)
                {
                    return browser_2;
                }
                else
                {
                    return browser_1;
                }
            }
        }

        private WebBrowser browser_1;
        private WebBrowser browser_2;

        /// <summary>
        /// Switch between two browser
        /// </summary>
        public Timer SwitchBrowserTimer { get; set; }

        #endregion

        public BidWebSaleEngine(String url)
        {
            
            Url = url;
            Running = false;
            IsEnded = false;

            Sale = new BidSale();
            Sale.StartDateTime = DateTime.Now;
            Sale.BidURL = url;
            MyTimer = new Timer();
            MyTimer.Interval = normalTickTime;
            MyTimer.Tick += new EventHandler(MyTimer_Tick);
            LastBidAmount = "";

            MapBidUserWeb = new Dictionary<String, BidUserWeb>();

            SwitchBrowserTimer = new Timer();
            SwitchBrowserTimer.Tick += new EventHandler(SwitchBrowser_Tick);
            SwitchBrowserInterval = DefaultSwitchInterval;

            int detailIndex = Sale.BidURL.IndexOf("/detail/");
            String bid_item = Sale.BidURL.Substring(detailIndex + "/detail/".Length, 6);

            Sale.BidFunIdentifier = bid_item;
            LastUserName = "";
            NbBids = 0;
            ERROR = false;
        }

        #region Start-Stop

        public void Start()
        {
            if (IsEnded == false && Running == false)
            {
                BidSaleManager.Instance.saveOrUpdate(Sale);

                Log.Info("Start " + Sale.BidFunIdentifier + " [" + Sale.Label + "] " + DateTime.Now);
                browser_1 = new WebBrowser();
                Browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
                Browser.Navigate(Sale.BidURL); // http://www.bidfun.fr/bin/detail/140086/400-points-bidfun.html
                Running = true;

                MyTimer.Tick -= MyTimer_Tick;
                MyTimer.Stop();
                MyTimer.Tick += new EventHandler(MyTimer_Tick);

                SwitchBrowserTimer.Start();
                LastBidAmount = "";

                Sale.StartDateTime = DateTime.Now;
                ERROR = false;
            }
        }

        public void Pause()
        {
            if (IsEnded == false)
            {
                if (Running == false)
                {
                    Start();
                }
                else
                {
                    Stop();
                }
            }

        }

        public void Stop()
        {
            Log.Info("Stop " + Sale.BidFunIdentifier + " [" + Sale.Label + "] " + DateTime.Now);
            MyTimer.Tick -= MyTimer_Tick;
            MyTimer.Stop();

            SwitchBrowserTimer.Tick -= SwitchBrowser_Tick;
            SwitchBrowserTimer.Stop();

            Running = false;
            ERROR = false;
        }

        #endregion


        private Boolean checkForSwitchingBrowser()
        {
            if (ERROR)
            {
                return true;
            }

            if (HTimeLeft != null)
            {
                if (HTimeLeft.InnerText == BidVerifString)
                {
                    return false;
                }
                try
                {
                    DateTime current = DateTime.Parse(HTimeLeft.InnerText);
                    DateTime oneMinutes = DateTime.Parse("00:00:40");
                    //  SwitchBrowser.Interval = SwitchBrowserInterval;
                    if (current > oneMinutes) // On a une minute faire le switch
                    {
                        Console.WriteLine("On a le temps pour switcher " + DateTime.Now);
                        SwitchBrowserInterval = DefaultSwitchInterval;
                        return true;
                    }
                    else
                    {
                        Console.WriteLine("Pas le temps pour switcher repost 1 minute " + DateTime.Now);
                        SwitchBrowserTimer.Stop();
                        SwitchBrowserInterval = 1000 * 60; // 1 minutes
                        SwitchBrowserTimer.Start();
                        return false;
                    }
                }
                catch (Exception mme)
                {
                    return false;
                }
            }
            return false;


        }

        public void SwitchBrowser_Tick(object sender, EventArgs e)
        {
            //Console.WriteLine("SwitchBrowser_Tick " + DateTime.Now);
            if (checkForSwitchingBrowser())
            {
                if (OnBrowserChange != null)
                {
                    OnBrowserChange();
                }

                if (browser_1 == null)
                {
                    //Console.WriteLine("SwitchBrowser_Tick browser_one == null ");
                    browser_1 = new WebBrowser();
                    browser_1.Visible = false;
                    browser_1.Navigate(Sale.BidURL);
                    browser_1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
                }
                else
                {
                    //Console.WriteLine("SwitchBrowser_Tick browser_two == null ");
                    browser_2 = new WebBrowser();
                    browser_2.Visible = false;
                    browser_2.Navigate(Sale.BidURL);
                    browser_2.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);
                }
            }
            //Console.WriteLine("SwitchBrowser_Tick End " + DateTime.Now);
        }

        void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            MyTimer.Stop();
            if (sender == browser_1)
            {
                //Stop browser two
                if (browser_2 != null)
                {
                    Log.Info("Working on WebB 1 Stoping WebB 2 " + ToString() + " " + DateTime.Now);
                    browser_2.Dispose();
                    browser_2.DocumentCompleted -= browser_DocumentCompleted;
                    browser_2 = null;
                }
            }
            else
            {
                //Stop browser one
                if (browser_1 != null)
                {
                    Log.Info("Working on WebB 2 Stoping WebB 1 " + ToString() + " " + DateTime.Now);
                    browser_1.Dispose();
                    browser_1.DocumentCompleted -= browser_DocumentCompleted;
                    browser_1 = null;
                }
            }
            Document = Browser.Document;
            InitHtmlElement();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url">http://www.bidfun.fr/bin/detail/140086/400-points-bidfun.html</param>
        private void InitHtmlElement()
        {
            try
            {
                if (Sale.BidObject.RealPrice == "0")
                {
                    int index = Document.Body.InnerHtml.IndexOf("class=items_list_price>\r\n<P><SPAN>") + "class=items_list_price>\r\n<P><SPAN>".Length + 1;
                    int endindex = Document.Body.InnerHtml.IndexOf("</SPAN></P></TD></TR>\r\n<TR>", index);
                    Sale.BidObject.RealPrice = Document.Body.InnerHtml.Substring(index, endindex - index).Replace("&nbsp;", "");
                    BidObjectManager.Instance.UpdateBidObject(Sale.BidObject);
                }
                HUser = Document.GetElementById(bid_span_user + Sale.BidFunIdentifier);
                HAmount = Document.GetElementById(bid_span_amount + Sale.BidFunIdentifier);
                HTimeLeft = Document.GetElementById(bid_span_time + Sale.BidFunIdentifier);

                HConnected = Document.GetElementById("connected");
                for (int i = 0; i < 10; i++)
                {
                    hlast_bids_username[i] = Document.GetElementById("last_bids_username_" + (i + 1).ToString());
                    hlast_bids_amount[i] = Document.GetElementById("last_bids_amount_" + (i + 1).ToString());
                    hlast_bids_type[i] = Document.GetElementById("last_bids_type_" + (i + 1).ToString());
                    hlast_bids_datetime[i] = Document.GetElementById("last_bids_datetime_" + (i + 1).ToString());
                }


                Boolean ret = postInitElement();

                if (HUser != null && HAmount != null && HTimeLeft != null && HConnected != null && ret == true)
                {
                    if (MyTimer.Enabled == false)
                    {
                        if (Sale.BidloggerList.Count == 0)
                        {
                            ThreadedParseDocument();
                        }
                        MyTimer.Start();
                    }
                }
                else
                {
                    Log.Error("Probleme dans InitHtml " + Sale.BidURL + " Detail : hUser [" + HUser + "] hAmount [" + HAmount + "] HTimeLeft [" + HTimeLeft + "] HConnected [" + HConnected + "]");
                    if (BidError != null)
                    {
                        BidError(this);
                    }
                    ERROR = true;
                }
            }
            catch (Exception mme)
            {
                ERROR = true;
            }
        }

        /// <summary>
        /// Verification de tout les element HTML avant le lancement du Timer
        /// </summary>
        /// <returns></returns>
        public virtual Boolean verifElementInDom()
        {
            if (HUser != null && HAmount != null && HTimeLeft != null && HConnected != null)
            {
                return true;
            }
            else
            {
                ERROR = true;
                Log.Error("ChechDomElement " + Sale.BidURL + " Detail : hUser [" + HUser + "] hAmount [" + HAmount + "] HTimeLeft [" + HTimeLeft + "] HConnected [" + HConnected + "]");
            }
            
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MyTimer_Tick(object sender, EventArgs e)
        {
            if (NewBid == true)
            {
                NewBid = false;
                if (OnLoggerUpdate != null)
                {
                    OnLoggerUpdate(Sale.BidloggerList[Sale.BidloggerList.Count - 1]);
                }
            }

            if (checkForNewBid() == true && verifElementInDom() ==true)
            {
                new System.Threading.Thread(new System.Threading.ThreadStart(ThreadedParseDocument)).Start();
            }
        }

        /// <summary>
        /// Internal Thread for parsing Document
        /// </summary>
        private void ThreadedParseDocument()
        {
            BidLogger log = null;
            parseDocument(Document, out log);
            if (log != null)
            {
#if DEBUG
                Log.Info("New Bid : " + log.Amount + " " + log.BidUser.Pseudo + " - " + log.BidSale.BidObject.Label + "W " + log.Winner + " " + log.BidOperatorType.Name + " [" + log.NbUserConnected + "]");
#endif
                NewBid = true;
            }
        }

        /// <summary>
        /// End bid
        /// Notify event, clear resources
        /// </summary>
        public void BidSaleWebEnded()
        {

            if (Sale.BidloggerList.Count != 0)
            {
                Sale.BidloggerList[Sale.BidloggerList.Count - 1].Winner = true;
                BidLoggerManager.Instance.saveOrUpdate(Sale.BidloggerList[Sale.BidloggerList.Count - 1]);
            }

            if (FirstVerification != TIME_NULL)
            {
                Sale.VerificationLapseTimeTick = (Int32)(BidEndedTime.Subtract(FirstVerification)).Ticks;
            }


            Sale.EndDateTime = DateTime.Now;
            MyTimer.Tick -= MyTimer_Tick;
            MyTimer.Stop();
            IsEnded = true;
            Running = false;

            Log.Info("Bid Sale Ended : " + Sale.BidFunIdentifier + " [" + Sale.Label + "] " + DateTime.Now + " VerificationLapseTimeTick (MS) " + (BidEndedTime.Subtract(FirstVerification)).TotalMilliseconds / 1000);

            try
            {
                StatsBidSale stat = BidSaleEndStatisticsCalculator.Instance.caculateStatisticsAtTheEndOfBidSale(Sale, MapBidUserWeb);
                BidSaleManager.Instance.saveOrUpdate(Sale);
                if (StatsHander != null)
                {
                    StatsHander(stat);
                }
            }
            catch (Exception mme)
            {
                Log.Error(mme);
            }

            if (bidEndedEvent != null)
            {
                bidEndedEvent(this);
            }

            Dispose();

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="htmlDocument"></param>
        private void parseDocument(HtmlDocument htmlDocument, out BidLogger newBid)
        {
            String amount = HAmount.InnerText;
            String span_time = HTimeLeft.InnerText;
            String userName = HUser.InnerText;
            NBUserConnected = HConnected.InnerText;

            if (userName == null)
            {
                Log.Error("parseDocument : userName = NULL " + ToString());
                newBid = null;
                return;
            }

            BidUser user = BidUserManager.Instance.getUserByPseudo(userName);

            if (user == null)
            {
                Log.Error("parseDocument : USER = NULL " + ToString());
                if (BidError != null)
                {
                    BidError(this);
                }
                newBid = null;
                return;
            }
            newBid = new BidLogger(amount);

            newBid.BidSale = Sale;
            newBid.BidUser = user;
            newBid.Span_time = span_time;

            newBid = updateBidLogger(newBid);
            if (HTimeLeft.InnerText.ToUpper().StartsWith(BidEndedString.ToUpper()))
            {
                newBid.Winner = true;
            }
            else
            {
                newBid.Winner = false;
            }

            Int32 nbc = 0;
            Int32.TryParse(NBUserConnected, out nbc);
            newBid.NbUserConnected = nbc;
            newBid.BidSale = this.Sale;

            Sale.BidloggerList.Add(newBid);

            LastBidAmount = amount; // Set the last Bid Amount  
            NbBids = Sale.BidloggerList.Count;
            LastUserName = user.Pseudo;

            lock (this)
            {
                BidLoggerManager.Instance.saveOrUpdate(newBid);
            }
            //Check for User
            if (MapBidUserWeb.ContainsKey(newBid.BidUser.Pseudo) == false)
            {
                MapBidUserWeb.Add(newBid.BidUser.Pseudo, new BidUserWeb(newBid.BidUser, 1, newBid.Amount, newBid.BidOperatorType.BidOperatorTypeAsEnum));
            }
            else
            {
                BidUserWeb temp_user;
                MapBidUserWeb.TryGetValue(newBid.BidUser.Pseudo, out temp_user);
                temp_user.NbBidInSale++;
                temp_user.Amount = newBid.Amount;
            }
        }

        /// <summary>
        /// Update BidType members
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        internal BidLogger updateBidLogger(BidLogger val)
        {
            for (int i = 0; i < 10; i++)
            {
                if (hlast_bids_amount[i] != null)
                {
                    if (val.Amount == hlast_bids_amount[i].InnerText)
                    {
                        val.BidOperatorType = BidOperatorTypeManager.Instance.getBidOperatorTypeByName(hlast_bids_type[i].InnerText);
                        val.DateTime = DateTime.Parse(hlast_bids_datetime[i].InnerText);
                        return val;
                    }
                }
            }
            val.BidOperatorType = BidOperatorTypeManager.Instance.getBidOperatorTypeByName("NONE");
            val.DateTime = DateTime.Now;
            return val;
        }

        public override string ToString()
        {
            return Sale.BidObject.Label + " [" + Sale.BidFunIdentifier + "]";
        }

        /// <summary>
        /// Clean Ressources
        /// </summary>
        public virtual void Dispose()
        {
            Log.Debug("Dispose [" + Sale.BidFunIdentifier + "]");

            Document = null;
            Browser.Dispose();

            browser_1 = null;
            browser_2 = null;

            MapBidUserWeb.Clear();
            MapBidUserWeb = null;

            HUser = null;
            HAmount = null;
            HTimeLeft = null;
            HConnected = null;
            for (int i = 0; i < 10; i++)
            {
                hlast_bids_username[i] = null;
                hlast_bids_amount[i] = null;
                hlast_bids_type[i] = null;
                hlast_bids_datetime[i] = null;
            }

            SwitchBrowserTimer.Tick -= SwitchBrowser_Tick;
            SwitchBrowserTimer.Stop();

            Sale.BidloggerList.Clear();
        }
    }
}
